/**
 ******************************************************************************
 * @file	epcsx.c
 * @brief	epcsx 存储器通信

驱动层采用 spidev 控制器驱动依赖 spi_mxs，该驱动忽略 cs_change，
每一个spi_ioc_transfer里的所有内容均不在似 cs 信号翻转，翻转只存在于末尾

bug
    连续write、read数小时驱动存在错误，大概374行
TODO:introduce
 *
 @section Platform
    -#
 @section Library
    -#
 *
 * @section Depend
 *	-# spi_mxs.ko
 *	-# spidev.ko

- 2016-10-18,MenglongWu,MenglongWoo@aliyun.com
*/
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>

#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>


#include "proginfo.h"
#include "epcsx.h"

#define ARRAY_SIZE(a)           (sizeof(a) / sizeof((a)[0]))

// EPCS命令

#define EPCS_WRITE_ENABLE       0x06
#define EPCS_WRITE_DISABLE      0x04

#define EPCS_READ_STATUS        0x05
#define EPCS_READ_BYTE          0x03
#define EPCS_READ_SILICON_ID    0xab  //读取silicon ID
#define EPCS_READ_SILICON_ID128 0x9f  //读取silicon ID

#define EPCS_WRITE_STATUE       0x01
#define EPCS_WRITE_BYTE         0x02

#define EPCS_ERASE_BULK         0xc7  //擦除所有扇区，大约需要5-10s
#define EPCS_ERASE_SECTOR       0xd8  //擦除指定扇区


static void         epcs_write_enable(struct epcs_dev *dev);
static unsigned int epcs_read_status(struct epcs_dev *dev);
static int          match(struct epcs_dev *dev, char pid);
static unsigned int epcs_read_silicon_id(struct epcs_dev *dev);

// #define DEF_SPI "/dev/spidev1.1"
// static const char *device = DEF_SPI;
// static uint8_t mode;
// static uint8_t bits = 8;
// static uint32_t speed = 500000;
// static uint16_t delay;


struct epcsx_table
{
    char          pid;
    char	 *desc;
    unsigned long num_sector;
    unsigned long page_per_sector;
    unsigned long byte_per_page;
    unsigned long sector_sz;
};

// 识别epcsx设备类型匹配参数
static const struct epcsx_table table[] = {

    {
	    .pid             = 0x13,  // GD25Q80C
	    .desc            = "8Mbit GD25Q80C",
	    .num_sector      = 256,
	    .page_per_sector = 16,       // 每页有256字节，这个pdf没有体现，pdf只说一个sector有4KB、一个page有256B
	    .byte_per_page   = 256,      // 每页有256字节，这个pdf没有体现，pdf只说一个sector有4KB、一个page有256B
	    .sector_sz       = 0x01000,  // 64K
                                         // block = 一个block里有16个sector
    },

    {
	    .pid             = 0x10,  // EPCS1 1Mbit
	    .desc            = "1Mbit EPCS1",
	    .num_sector      = 4,
	    .page_per_sector = 128,
	    .byte_per_page   = 0x100,    // 256B
	    .sector_sz       = 0x08000,  // 32KB
    },
    {
	    .pid             = 0x1f4,  // EPCS1 1Mbit
	    .desc            = "1Mbit EPCS16",
	    .num_sector      = 8,  // 4,
	    .page_per_sector = 128,
	    .byte_per_page   = 0x100,    // 256B
	    .sector_sz       = 0x08000,  // 32KB
    },

    {
	    .pid             = 0x12,  // EPCS4 4Mbit
	    .desc            = "4Mbit EPCS4",
	    .num_sector      = 8,
	    .page_per_sector = 256,
	    .byte_per_page   = 0x100,    // 256B
	    .sector_sz       = 0x10000,  // 64KB
    },
    {
	    .pid             = 0x14,  // EPCS16
	    .desc            = "32Mbit EPCS16",
	    .num_sector      = 32,
	    .page_per_sector = 256,
	    .byte_per_page   = 0x100,    // 256B
	    .sector_sz       = 0x10000,  // 64KB
    },
    {
	    .pid             = 0x16,  // EPCS64
	    .desc            = "64Mbit EPCS64",
	    .num_sector      = 128,
	    .page_per_sector = 256,
	    .byte_per_page   = 0x100,    // 256B
	    .sector_sz       = 0x10000,  // 64KB
    },
    {
	    .pid             = 0x18,  // EPCS128
	    .desc            = "128Mbit EPCS128",
	    .num_sector      = 64,
	    .page_per_sector = 1024,
	    .byte_per_page   = 0x100,    // 256B
	    .sector_sz       = 0x40000,  // 256KB
    },
};


int epcs_close(struct epcs_dev *dev)
{
    assert(dev != NULL);
    close(dev->fd);
    free(dev);
    dev = NULL;
    return 0;
}


int epcs_open(char *device, struct epcs_dev **dev, unsigned long ispeed)
{
    // todo spinlock
    unsigned char mode  = 0;
    unsigned char bits  = 8;
    unsigned long speed = 12 * 1000 * 1000;  // 12MHz
    // unsigned short delay;
    int fd;

    if (ispeed) {
	speed = ispeed;
    }

    fd = open(device, O_RDWR);
    if (fd == 0) {
	return -1;
    }


    ioctl(fd, SPI_IOC_WR_MODE, &mode);
    ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    struct epcs_dev *pdev;
    pdev = (struct epcs_dev *)malloc(sizeof(struct epcs_dev));
    if (pdev == NULL) {
	goto _fail1;
    }
    bzero(pdev, sizeof(struct epcs_dev));
    // todo spinlock
    pdev->fd = fd;
    if (-1 == epcs_read_silicon_id(pdev)) {
	goto _fail2;
    }
    *dev = pdev;
    return 0;
_fail1:
    close(fd);
    return -1;
_fail2:
    epcs_close(pdev);
    return -2;
}


/**
 * @brief	写使能
 * @param[in]	dev 设备描述符
 * @remarks 任何与写入相关的操作在写入前必须打开写入使能
 * @see	epcs_erase_bulk
 * @see	epcs_erase_sector
 * @see	epcs_write_page
 */

static void epcs_write_enable(struct epcs_dev *dev)
{
    uint8_t                 tx[]  = { EPCS_WRITE_ENABLE };
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)NULL,
		.len    = ARRAY_SIZE(tx),
	},
    };
    // todo spinlock
    ioctl(dev->fd, SPI_IOC_MESSAGE(1), &arr);
    // todo spinlock
}

/**
 * @brief	获取设备状态
 * @param[in]	dev 设备描述
 * @retval	-1 总线错误
         0：表示处于空闲模式\n
         3: 表示芯片正在执行写入、擦除状态，不能接受除EPCS_READ_STATUS意外的指令
 * @remarks
 */

static unsigned int epcs_read_status(struct epcs_dev *dev)
{
    uint8_t                 tx[] = { EPCS_READ_STATUS };
    uint8_t                 rx[1];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };
    int ret;

    ret = ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
    if (ret == -1) {
	return -1;
    }
    return rx[0];
}

/**
 * @brief	查询是否设备能匹配epcsx，匹配则填充设备描述符
 * @param[in]	dev 设备描述
 * @param[in]	pid 扫描到的设备ID
 * @retval	0 匹配
 * @retval	-1 不匹配
 */

static int match(struct epcs_dev *dev, char pid)
{
    int i;
    printf("pid = %x----\r\n", pid);
    for (i = 0; i < ARRAY_SIZE(table); i++) {
	if (table[i].pid == pid) {
	    break;
	}
    }
    if (ARRAY_SIZE(table) != i) {
	dev->pid             = table[i].pid;
	dev->desc            = table[i].desc;
	dev->num_sector      = table[i].num_sector;
	dev->page_per_sector = table[i].page_per_sector;
	dev->byte_per_page   = table[i].byte_per_page;
	dev->sector_sz       = table[i].sector_sz;
	return 0;
    }
    else {
	dev->desc = "unknow";
    }
    return -1;
}

/**
 * @brief	探测设备ID
 * @param[in]	dev 设备描述
 * @retval	0 找到
 * @retval	-1 没找到
 */

static unsigned int epcs_read_silicon_id(struct epcs_dev *dev)
{
    uint8_t                 tx[] = { EPCS_READ_SILICON_ID, 0x00, 0x00, 0x00 };
    uint8_t                 rx[1];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };

    ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
    if (0 == match(dev, rx[0])) {
	return 0;
    }

    tx[0] = EPCS_READ_SILICON_ID128;
    ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
    if (0 == match(dev, rx[0])) {
	return 0;
    }
    return -1;
}

int epcs_erase_bulk(struct epcs_dev *dev)
{
    // return 0;
    epcs_write_enable(dev);
    uint8_t                 tx[]  = { EPCS_ERASE_BULK };
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)NULL,
		.len    = ARRAY_SIZE(tx),
	},
    };
    // todo spinlock
    ioctl(dev->fd, SPI_IOC_MESSAGE(1), &arr);

    int count = 20;
    do {
	sleep(1);
    } while (epcs_read_status(dev) != 0 && --count);
    if (count == 0) {
	return -1;
    }
    else {
	return 0;
    }
}


int epcs_erase_sector(struct epcs_dev *dev, unsigned int addr)
{
    // return 0;
    epcs_write_enable(dev);
    uint8_t                 tx[]  = { EPCS_ERASE_SECTOR,
                     (uint8_t)(addr >> 16),
                     (uint8_t)(addr >> 8),
                     (uint8_t)addr };
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)NULL,
		.len    = ARRAY_SIZE(tx),
	},
    };
    // todo spinlock
    ioctl(dev->fd, SPI_IOC_MESSAGE(1), &arr);

    int count = 100;
    while (epcs_read_status(dev) != 0 && --count) {
	usleep(20000);
    }
    if (count == 0) {
	return -1;
    }
    else {
	return 0;
    }
}


int epcs_write_page(struct epcs_dev *dev, unsigned int addr, unsigned char *data, unsigned int len)
{
    epcs_write_enable(dev);

    uint8_t                 tx[]  = { EPCS_WRITE_BYTE,
                     (uint8_t)(addr >> 16),
                     (uint8_t)(addr >> 8),
                     (uint8_t)addr };
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)NULL,
		.len    = ARRAY_SIZE(tx),
	},
	{
		.tx_buf = (unsigned long)data,
		.rx_buf = (unsigned long)NULL,
		.len    = len,
	},
    };
    ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);

    int count = 1000000;
    while (epcs_read_status(dev) != 0 && --count)
	;
    if (count == 0) {
	return -1;
    }
    return 0;
}


int epcs_read_page(struct epcs_dev *dev, unsigned int addr, unsigned char *data, unsigned int len)
{
    uint8_t                 tx[]  = { EPCS_READ_BYTE,
                     (uint8_t)(addr >> 16),
                     (uint8_t)(addr >> 8),
                     (uint8_t)addr };
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)data,
		.len       = len,
		.cs_change = 0,
	},
    };
    int count = 1000000;
    while (epcs_read_status(dev) != 0 && --count)
	;
    if (count == 0) {
	return -1;
    }
    return ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
}

unsigned int epcs_size(struct epcs_dev *dev)
{
    return dev->byte_per_page * dev->page_per_sector * dev->num_sector;
}


#ifdef PRJ_NAME
PROG_INFO(project, PRJ_NAME);
#endif

#if defined(PRJ_VERSION) && defined(PRJ_PATCHLEVEL) && defined(PRJ_SUBLEVEL)
PROG_INFO(version, PRJ_VERSION "." PRJ_PATCHLEVEL "." PRJ_SUBLEVEL);
#endif

#ifdef BUILD_DATE
PROG_INFO(build, BUILD_DATE);
#endif

PROG_INFO(Author, "MenglongWu");
PROG_INFO(depend, "spi_mxs.ko spidev.ko");
PROG_INFO(description, "The serial configuration devices EPCSx interface");
